﻿using System;
using WinRTFramework.Common;
using WinRTFramework.FrameworkConfiguration;
using WinRTFramework.Interfaces.AppStates;
using WinRTFramework.Interfaces.Navigation.Page;
using WinRTFramework.Interfaces.Navigation.ViewModel;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace WinRTFramework.FrameworkCore.Managers
{
    internal class NavigationManager
    {
        private readonly IServiceInjector _serviceInjector;
        private readonly IApplicationAdapter _applicationAdapter;
        private readonly Frame _rootFrame;
        IViewViewModelMapper _viewProvider;

        private const string NavigationParameterFrameStateKey = "NavigationParameter";

        private static DependencyProperty PageNavigationParameterProperty =
            DependencyProperty.RegisterAttached("PageNavigationParameter", typeof(object), typeof(NavigationManager), null);


        public class NavigationManagerOptions
        {
            public bool HandleResumeAndSuspend { get; set; }
        }

        private NavigationManager(IApplicationAdapter applicationAdapter, Frame rootFrame, IViewViewModelMapper viewProvider, IServiceInjector serviceInjector)
        {
            _serviceInjector = serviceInjector;
            _applicationAdapter = applicationAdapter;
            _rootFrame = rootFrame;
            _viewProvider = viewProvider;

            if (rootFrame is CustomFrame)
            {
                (rootFrame as CustomFrame).BeforeNavigated += rootFrame_Navigated;
            }
            else
            {
                rootFrame.Navigated += rootFrame_Navigated;
            }

            rootFrame.Navigating += rootFrame_Navigating;
            rootFrame.NavigationFailed += rootFrame_NavigationFailed;
            rootFrame.NavigationStopped += rootFrame_NavigationStopped;
        }

        private void ListenForApplicationSuspendAndResume(IApplicationAdapter applicationAdapter)
        {
            applicationAdapter.AfterResume += Application_AfterResume;
            applicationAdapter.BeforeSuspend += Application_BeforeSuspend;
        }

        public static NavigationManager CreateFrom (IApplicationAdapter applicationAdapter, Frame rootFrame, IViewViewModelMapper viewProvider, IServiceInjector serviceInjector, NavigationManagerOptions options)
        {
            var navigationManager = new NavigationManager(applicationAdapter, rootFrame, viewProvider, serviceInjector);

            if(options.HandleResumeAndSuspend)
                navigationManager.ListenForApplicationSuspendAndResume(applicationAdapter);

            return navigationManager;
        }

        void Application_BeforeSuspend(object sender, EventArgs e)
        {
            var currentPage = _rootFrame.Content as Page;
            if (currentPage == null)
                return;

            TrySaveTransientStateOnCurrentPage(currentPage);
            TryCallSuspendingOnCurrentPage(currentPage);
            TrySaveNavigationParameterOfCurrentPage(currentPage);
        }

        void Application_AfterResume(object sender, ResumePreviousAppState previousAppState)
        {
            var currentPage = _rootFrame.Content as Page;
            if (currentPage == null)
                return;

            if (previousAppState == ResumePreviousAppState.Terminated)
            {
                var lastNavigationParameter = TryRetrieveNavigationParameterFromLastPageBeforeTerminate(currentPage);
                currentPage.SetValue(PageNavigationParameterProperty, lastNavigationParameter);

                TryApplyViewModelToCurrentPage(currentPage, lastNavigationParameter);
                TryCallResumedOnCurrentPage(currentPage, previousAppState);
                TryLoadTransientStateOnCurrentPage(currentPage);
                TryCallOnReadyOnCurrentPage(currentPage);
                return;
            }

            TryCallResumedOnCurrentPage(currentPage, previousAppState);
        }

        private void TryCallOnReadyOnCurrentPage(Page currentPage)
        {
            var careAboutReady = currentPage as ICareAboutReady;
            if (careAboutReady == null)
                return;

            careAboutReady.OnReady();
        }

        private object TryRetrieveNavigationParameterFromLastPageBeforeTerminate(Page currentPage)
        {
            var frameState = SuspensionManager.SessionStateForFrame(currentPage.Frame);
            return frameState[NavigationParameterFrameStateKey];
        }

        private void TrySaveNavigationParameterOfCurrentPage(Page currentPage)
        {
            var frameState = SuspensionManager.SessionStateForFrame(currentPage.Frame);
            frameState[NavigationParameterFrameStateKey] = currentPage.GetValue(PageNavigationParameterProperty);
        }

        private void TryCallResumedOnCurrentPage(Page currentPage, ResumePreviousAppState previousAppState)
        {
            var resumedSupporter = currentPage.DataContext as ICareAboutSuspendResume;
            if (resumedSupporter == null)
                return;

            var navigationParameter = currentPage.GetValue(PageNavigationParameterProperty);

            resumedSupporter.Resumed(navigationParameter, previousAppState);
        }

        private void TryCallSuspendingOnCurrentPage(Page currentPage)
        {
            var suspendSupporter = currentPage.DataContext as ICareAboutSuspendResume;
            if (suspendSupporter == null)
                return;

            suspendSupporter.Suspending();
        }

        private Page _previousPage;
        void rootFrame_Navigated(object sender, NavigationEventArgs e)
        {
            var currentPage = e.Content as Page;
            if (currentPage == null)
                return;

            currentPage.SetValue(PageNavigationParameterProperty, e.Parameter);

            TryApplyViewModelToCurrentPage(currentPage, e.Parameter);
            TryCallNavigatedFromOnPreviousPage();
            TryCallNavigatedToOnCurrentPage(currentPage, e.Parameter);

            if (e.NavigationMode == NavigationMode.New)
                ClearAllTransientStatesFromForwardViewModels(currentPage);
            else
                TryLoadTransientStateOnCurrentPage(currentPage);

            TryCallOnReadyOnCurrentPage(currentPage);
        }

        void rootFrame_Navigating(object sender, Windows.UI.Xaml.Navigation.NavigatingCancelEventArgs e)
        {
            var currentPage = _rootFrame.Content as Page;
            if (currentPage == null)
            {
                _previousPage = null;
                return;
            }

            TrySaveTransientStateOnCurrentPage(currentPage);
            TryCallNavigatingFromOnCurrentPage(currentPage);

            _previousPage = currentPage;
        }

        void rootFrame_NavigationStopped(object sender, NavigationEventArgs e)
        {
        }

        void rootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
        {
        }

        private void ClearAllTransientStatesFromForwardViewModels(Page currentPage)
        {
            var frameState = SuspensionManager.SessionStateForFrame(currentPage.Frame);
            string viewModelKey = BuildViewModelTransientStateKey(currentPage.Frame.BackStackDepth);

            var nextPageKey = viewModelKey;
            int nextPageIndex = currentPage.Frame.BackStackDepth;
            while (frameState.Remove(nextPageKey))
            {
                nextPageIndex++;
                nextPageKey = BuildViewModelTransientStateKey(nextPageIndex);
            }
        }

        private static string BuildViewModelTransientStateKey(int backStackDepth)
        {
            return "ViewModel-" + backStackDepth;
        }

        private void TryLoadTransientStateOnCurrentPage(Page currentPage)
        {
            ISupportTransientState transientStateSupporter = currentPage.DataContext as ISupportTransientState;
            if (transientStateSupporter == null)
                return;

            string viewModelKey = BuildViewModelTransientStateKey(currentPage.Frame.BackStackDepth);

            var frameState = SuspensionManager.SessionStateForFrame(currentPage.Frame);
            object transientState = frameState[viewModelKey];
            transientStateSupporter.UpdateTransientState(transientState);
        }

        private void TrySaveTransientStateOnCurrentPage(Page currentPage)
        {
            var transientStateProvider = currentPage.DataContext as ISupportTransientState;
            if (transientStateProvider == null)
                return;

            object transientState = transientStateProvider.GetTransientState();
            string viewModelKey = BuildViewModelTransientStateKey(currentPage.Frame.BackStackDepth);
            var frameState = SuspensionManager.SessionStateForFrame(currentPage.Frame);
            frameState[viewModelKey] = transientState;
        }

        private void TryInitializeViewModelOfCurrentPage(object viewModel)
        {
            if (viewModel == null)
                return;

            _serviceInjector.InjectServicesIntoViewModel(viewModel);
        }

        private void TryApplyViewModelToCurrentPage(FrameworkElement currentPage, object navigationParameter)
        {
            var viewModel = _viewProvider.GetViewModelForView(currentPage.GetType(), navigationParameter);
            TryInitializeViewModelOfCurrentPage(viewModel);
            currentPage.DataContext = viewModel;
        }

        private static void TryCallNavigatedToOnCurrentPage(FrameworkElement currentPage, object navigationParameter)
        {
            var navigationFlowReceiver = currentPage.DataContext as ICareAboutNavigationFlow;
            if (navigationFlowReceiver == null)
                return;

            navigationFlowReceiver.OnNavigatedTo(navigationParameter);
        }

        private void TryCallNavigatingFromOnCurrentPage(Page currentPage)
        {
            var navigationFlowReceiver = currentPage.DataContext as ICareAboutNavigationFlow;
            if (navigationFlowReceiver == null)
                return;

            navigationFlowReceiver.OnNavigatingFrom();
        }

        private void TryCallNavigatedFromOnPreviousPage()
        {
            if (_previousPage == null)
                return;

            var navigationFlowReceiver = _previousPage.DataContext as ICareAboutNavigationFlow;
            if (navigationFlowReceiver == null)
                return;

            navigationFlowReceiver.OnNavigatedFrom();
        }
    }
}
